/**
 * 路由跳转方法，该方法相对于直接使用uni.xxx的好处是使用更加简单快捷
 * 并且带有路由拦截功能
 */

class Router {
	constructor() {
		// 原始属性定义
		this.config = {
			type: 'navigateTo',
			path: '',
			animationType: 'pop-in', // 窗口动画,只在APP有效
			animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
			intercept: true // 是否需要拦截
		}
		// 因为push方法是需要对外赋值给另外的对象使用，同时push内部有使用this，会导致push失去上下文
		// 这里在构造函数中进行this绑定
		this.push = this.push.bind(this)
		this.next = this.next.bind(this)

		// tab页面
		this.tabBars = []
		const pages = import.meta.glob("@/pages.json", { eager: true });
		const tabBar = Object.values(pages)[0].tabBar
		if(tabBar) {
			this.tabBars = tabBar.list.map(item => {
				return this.addRootPath(item.pagePath);
			})
			// 在H5中只要在pages.json中的tabBar中定义的pagePath，都算是tabbar page，所以midButton中的pagePath也是tabbar page
			// #ifdef H5
			if (tabBar.midButton && tabBar.midButton.pagePath) this.tabBars.push(tabBar.midButton.pagePath)
			// #endif
		}

		// 页面Map， name->path
		this.pageMap = new Map();
		// 主包页面
		const pageList = Object.values(pages)[0].pages
		pageList.map(item => this.pageMap.set(item.name || item.path, item.path))
		// 子包页面
		const subPackages = Object.values(pages)[0].subPackages
		if(subPackages) {
			subPackages.map(item => {
				item.pages.map(p => this.pageMap.set(p.name || item.root+this.addRootPath(p.path), item.root+this.addRootPath(p.path)))
			})
		}
		// 最后一个路由跳转参数
		this.lastConfig = {}
		// 路由拦截器
		this.interceptor = undefined
		// 白名单
		this.whiteList = []
		this.interceptList = []
		// 页面参数
		this.pageParams = new Map()
		// 页面返回携带参数
		this._backParams = {}

		// #ifdef H5
		// h5刷新，初始化参数
		if(location.hash){
			// hash模式
			this.config.path = location.hash.substring(1);
			this.config.type = this.getPageType(this.config.path)
			let pathParams = this.config.path.split('?')[1];
			if(pathParams){
				this.config.params = JSON.parse('{"' + decodeURIComponent(pathParams).replace(/&/g, '","').replace(/=/g, '":"') + '"}')
			}
		} else {
			// history 模式
			this.config.path = location.pathname
			this.config.type = this.getPageType(this.config.path)
			if(location.search){
				this.config.params = JSON.parse('{"' + decodeURIComponent(location.search.substring(1)).replace(/&/g, '","').replace(/=/g, '":"') + '"}')
			}
		}
		// #endif

		// 页面跳转传递的路由参数
		Object.defineProperty(this, 'params', {
			get() {
				let pages = getCurrentPages()
				let page = pages[pages.length - 1]
				if(!page) return {}
				let option = (page.$page?.options ? page.$page.options : page.options)
				// 兼容小程序  setup中无法获取参数，需要在onLoad后才能获取参数
				if(!option || Object.keys(option).length === 0)  return this.pageParams.get(this.addRootPath(this.removeParams(page.route))) || {}
				return option
			},
			set() {
				throw new Error("params为只读属性")
			},
			enumerable: false,
			configurable: false
		})
		// 调用back返回页面时传递的参数，一般用于返回页面触发方法，backParams只能使用一次
		Object.defineProperty(this, 'backParams', {
			get() {
				let temp = this._backParams
				this._backParams = undefined
				return temp
			},
			set() {
				throw new Error("backParams为只读属性")
			},
			enumerable: false,
			configurable: false
		})
	}


	// 判断path前面是否有"/"，如果没有则加上，否则无法跳转
	addRootPath(path) {
		return path[0] === '/' ? path : `/${path}`
	}

	// 整合路由参数
	mixinParams(path, params) {
		// path可能是name，根据name获取路径
		if (this.pageMap.get(path)) path = this.pageMap.get(path)
		path = path && this.addRootPath(path)

		// 使用正则匹配，主要依据是判断是否有"/","?","="等，如“/page/index/index?name=mary"
		// 如果有path中有get参数，转换后无需带上"?"
		let query = ''
		if (/.*\/.*\?.*=.*/.test(path)) {
			// object对象转为get类型的参数
			query = uni.$xl.queryParams(params, false)
			// 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
			return path += `&${query}`
		}
		// 直接拼接参数，因为此处path中没有后面的query参数，也就没有"?/&"之类的符号
		query = uni.$xl.queryParams(params)
		return path += query
	}

	// 去除参数路径
	removeParams(path) {
		return path.split('?')[0]
	}

	// 设置页面类型，判断是否tabbar页面，是则用switchTab
	getPageType(path) {
		if (path === '/' || this.tabBars.some(item => path.startsWith(item))) {
			return 'switchTab'
		} else {
			return 'navigateTo'
		}
	}

	// 设置白名单
	setWhiteList(whiteList) {
		this.whiteList = whiteList.map(item => {
			return new RegExp('^'+item.replace(/\*\*/g, '.*')+'$')
		})
	}
	setInterceptList(interceptList) {
		this.interceptList = interceptList.map(item => {
			return new RegExp('^'+item.replace(/\*/g,'.*')+'$')
		})
	}

	// 设置路由守卫，传参(to, from, next) => {}
	beforeEach(interceptor) {
		this.interceptor = interceptor
	}

	// 路由跳转方法
	// @param options 可以是路径字符串，也可以是对象{path: '', params: {}, type: '', intercept: true/false}
	async push(options = {}, params = {}) {
		// 合并用户的配置和内部的默认配置
		let mergeConfig = {}

		if (typeof options === 'string') {
			// 如果options为字符串，则为push(path, params)的形式
			mergeConfig.path = this.mixinParams(options, params)
			mergeConfig.params = params || {}
		} else {
			// 否则正常使用options中的path和params进行拼接
			mergeConfig.params = options.params || params || {}
			mergeConfig.path = this.mixinParams(options.name || options.path, mergeConfig.params)
		}
		// 如果本次跳转的路径和本页面路径一致，不执行跳转，防止用户快速点击跳转按钮，造成多次跳转同一个页面的问题
		if (mergeConfig.path === uni.$xl.currentPage()) return

		// 如果没有传type，则自动判断页面类型，只有switchTab与navigateTo两种类型
		mergeConfig.type = options.type ? options.type : this.getPageType(mergeConfig.path)

		// 如果没有传是否拦截，则为true默认拦截，如果有传，则使用用户传递的值
		mergeConfig.intercept = options.intercept == undefined ? true: options.intercept

		// 合并内外部参数
		mergeConfig = uni.$xl.deepMerge(mergeConfig, this.config)

		// 记录最后路由跳转的配置，若拦截通过，直接使用该配置打开
		this.lastConfig = mergeConfig

		// 判断是否拦截，以及用户是否定义了拦截器，并且不在白名单中
		const to = this.removeParams(mergeConfig.path)
		if (mergeConfig.intercept && this.interceptor && !this.whiteList.some(item => item.test(to))) {
			const from = this.removeParams(uni.$xl.currentPage())
			const isNext = await this.interceptor(mergeConfig, from, this.next)
			// 如果isNext为true，则执行路由跳转
			isNext && this.openPage(mergeConfig)
		} else {
			this.openPage(mergeConfig)
		}
	}


	async next(options = {}, params = {}) {
		if (Object.keys(options).length == 0){
			this.openPage(this.lastConfig)
		} else {
			this.push(options, params)
		}
	}

	currentPage() {
		return uni.$xl.currentPage()
	}

	isTabbarPage() {
		let path = this.currentPage();
		path = path && this.addRootPath(path)
		return path === '/' || this.tabBars.some(item => path.indexOf(item) != -1);
	}

	back(delta=1, defaultPath='/pages/index/index', params={}){
		let pages = getCurrentPages()
		if(pages.length <= delta ) {
			if(defaultPath)  this.push({path: defaultPath})
		} else {
			this._backParams = params
			uni.navigateBack({
				delta: delta
			})
		}
	}

	skipBack(noBacks= [], defaultPath='pages/index/index'){
		noBacks = typeof noBacks === 'string' ? noBacks ? [noBacks] : [] : noBacks
		const filterPages = noBacks.map(v => {
			// path可能是name，根据name获取路径
			if (this.pageMap.get(v)) return this.pageMap.get(v)
			return v
		})
		let step = 1;
		let pages = getCurrentPages();
		// pages.map(page => { console.log(page.route)})
		let currentPage = pages[pages.length-1];
		// 页面栈，应该倒着处理
		for (let i = pages.length-2; i>=0; i--) {
			// 若为登录注册修改密码等页面，以及vip，支付页面，则跳过返回
			if(filterPages.length > 0 && filterPages.includes(pages[i].route)){
				step++;
			}else if(currentPage.route == pages[i].route){
				// 若是当前页面，则继续跳过
				step++;
			}else{
				break;
			}
		}
		this.back(step, defaultPath)
	}

	// 执行路由跳转
	openPage(config) {
		// 设置路径
		this.path = config.path
		this.pageParams.set(this.removeParams(this.path), config.params)
		// 清空返回参数
		this._backParams = {}
		// 设置页面类型，判断是switchTab还是普通页面
		this.type = config.type
		// 解构参数
		const {
			path,
			animationType,
			animationDuration
		} = config

		if (config.type == 'navigateTo' || config.type == 'to') {
			uni.navigateTo({
				url: path,
				animationType,
				animationDuration
			})
		}
		if (config.type == 'redirectTo' || config.type == 'redirect') {
			uni.redirectTo({
				url: path
			})
		}
		if (config.type == 'switchTab' || config.type == 'tab') {
			uni.switchTab({
				url: path
			})
		}
		if (config.type == 'reLaunch' || config.type == 'launch') {
			uni.reLaunch({
				url: path
			})
		}
	}
}

const router = new Router()


export default router
